 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.keys;

 import java.io.IOException ;
 import java.io.Reader ;
 import java.io.StringReader ;
 import java.io.StringWriter ;
 import java.io.Writer ;
 import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.commands.Command;
 import org.eclipse.core.commands.ParameterizedCommand;
 import org.eclipse.core.commands.common.HandleObject;
 import org.eclipse.core.commands.common.NotDefinedException;
 import org.eclipse.core.commands.util.Tracing;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionDelta;
 import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.core.runtime.IRegistryChangeEvent;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.jface.bindings.Binding;
 import org.eclipse.jface.bindings.BindingManager;
 import org.eclipse.jface.bindings.Scheme;
 import org.eclipse.jface.bindings.keys.IKeyLookup;
 import org.eclipse.jface.bindings.keys.KeyBinding;
 import org.eclipse.jface.bindings.keys.KeyLookupFactory;
 import org.eclipse.jface.bindings.keys.KeySequence;
 import org.eclipse.jface.bindings.keys.KeyStroke;
 import org.eclipse.jface.bindings.keys.ParseException;
 import org.eclipse.jface.bindings.keys.SWTKeySupport;
 import org.eclipse.jface.contexts.IContextIds;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.swt.SWT;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IWorkbenchPreferenceConstants;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.WorkbenchException;
 import org.eclipse.ui.XMLMemento;
 import org.eclipse.ui.commands.ICommandService;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.misc.Policy;
 import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
 import org.eclipse.ui.internal.services.PreferencePersistence;
 import org.eclipse.ui.keys.IBindingService;

 /**
  * <p>
  * A static class for accessing the registry and the preference store.
  * </p>
  *
  * @since 3.1
  */
 final class BindingPersistence extends PreferencePersistence {

     /**
      * Whether this class should print out debugging information when it reads
      * in data, or writes to the preference store.
      */
     private static final boolean DEBUG = Policy.DEBUG_KEY_BINDINGS;

     /**
      * The index of the active scheme configuration elements in the indexed
      * array.
      *
      * @see BindingPersistence#read()
      */
     private static final int INDEX_ACTIVE_SCHEME = 0;

     /**
      * The index of the binding definition configuration elements in the indexed
      * array.
      *
      * @see BindingPersistence#read()
      */
     private static final int INDEX_BINDING_DEFINITIONS = 1;

     /**
      * The index of the scheme definition configuration elements in the indexed
      * array.
      *
      * @see BindingPersistence#read(BindingManager, ICommandService)
      */
     private static final int INDEX_SCHEME_DEFINITIONS = 2;

     /**
      * The name of the default scope in 2.1.x.
      */
     private static final String LEGACY_DEFAULT_SCOPE = "org.eclipse.ui.globalScope"; //$NON-NLS-1$

     /**
      * A look-up map for 2.1.x style <code>string</code> keys on a
      * <code>keyBinding</code> element.
      */
     private static final Map r2_1KeysByName = new HashMap ();

     static {
         final IKeyLookup lookup = KeyLookupFactory.getDefault();
         r2_1KeysByName.put(IKeyLookup.BACKSPACE_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.BACKSPACE_NAME));
         r2_1KeysByName.put(IKeyLookup.TAB_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.TAB_NAME));
         r2_1KeysByName.put(IKeyLookup.RETURN_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.RETURN_NAME));
         r2_1KeysByName.put(IKeyLookup.ENTER_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ENTER_NAME));
         r2_1KeysByName.put(IKeyLookup.ESCAPE_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ESCAPE_NAME));
         r2_1KeysByName.put(IKeyLookup.ESC_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ESC_NAME));
         r2_1KeysByName.put(IKeyLookup.DELETE_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.DELETE_NAME));
         r2_1KeysByName.put(IKeyLookup.SPACE_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.SPACE_NAME));
         r2_1KeysByName.put(IKeyLookup.ARROW_UP_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ARROW_UP_NAME));
         r2_1KeysByName.put(IKeyLookup.ARROW_DOWN_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ARROW_DOWN_NAME));
         r2_1KeysByName.put(IKeyLookup.ARROW_LEFT_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ARROW_LEFT_NAME));
         r2_1KeysByName.put(IKeyLookup.ARROW_RIGHT_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.ARROW_RIGHT_NAME));
         r2_1KeysByName.put(IKeyLookup.PAGE_UP_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.PAGE_UP_NAME));
         r2_1KeysByName.put(IKeyLookup.PAGE_DOWN_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.PAGE_DOWN_NAME));
         r2_1KeysByName.put(IKeyLookup.HOME_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.HOME_NAME));
         r2_1KeysByName.put(IKeyLookup.END_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.END_NAME));
         r2_1KeysByName.put(IKeyLookup.INSERT_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.INSERT_NAME));
         r2_1KeysByName.put(IKeyLookup.F1_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F1_NAME));
         r2_1KeysByName.put(IKeyLookup.F2_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F2_NAME));
         r2_1KeysByName.put(IKeyLookup.F3_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F3_NAME));
         r2_1KeysByName.put(IKeyLookup.F4_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F4_NAME));
         r2_1KeysByName.put(IKeyLookup.F5_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F5_NAME));
         r2_1KeysByName.put(IKeyLookup.F6_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F6_NAME));
         r2_1KeysByName.put(IKeyLookup.F7_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F7_NAME));
         r2_1KeysByName.put(IKeyLookup.F8_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F8_NAME));
         r2_1KeysByName.put(IKeyLookup.F9_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F9_NAME));
         r2_1KeysByName.put(IKeyLookup.F10_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F10_NAME));
         r2_1KeysByName.put(IKeyLookup.F11_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F11_NAME));
         r2_1KeysByName.put(IKeyLookup.F12_NAME, lookup
                 .formalKeyLookupInteger(IKeyLookup.F12_NAME));
     }

     /**
      * Converts a 2.1.x style key sequence (as parsed from the
      * <code>string</code> attribute of the <code>keyBinding</code>) to a
      * 3.1 key sequence.
      *
      * @param r21KeySequence
      * The sequence of 2.1.x key strokes that should be converted
      * into a 3.1 key sequence; never <code>null</code>.
      * @return A 3.1 key sequence; never <code>null</code>.
      */
     private static final KeySequence convert2_1Sequence(int[] r21KeySequence) {
         final int r21KeySequenceLength = r21KeySequence.length;
         final KeyStroke[] keyStrokes = new KeyStroke[r21KeySequenceLength];
         for (int i = 0; i < r21KeySequenceLength; i++) {
             keyStrokes[i] = convert2_1Stroke(r21KeySequence[i]);
         }

         return KeySequence.getInstance(keyStrokes);
     }

     /**
      * Converts a 2.1.x style key stroke (as parsed from the <code>string</code>
      * attribute of the <code>keyBinding</code> to a 3.1 key stroke.
      *
      * @param r21Stroke
      * The 2.1.x stroke to convert; must never be <code>null</code>.
      * @return A 3.1 key stroke; never <code>null</code>.
      */
     private static final KeyStroke convert2_1Stroke(final int r21Stroke) {
         return SWTKeySupport.convertAcceleratorToKeyStroke(r21Stroke);
     }

     /**
      * Returns the default scheme identifier for the currently running
      * application.
      *
      * @return The default scheme identifier (<code>String</code>); never
      * <code>null</code>, but may be empty or point to an undefined
      * scheme.
      */
     static final String getDefaultSchemeId() {
         final IPreferenceStore store = PlatformUI.getPreferenceStore();
         return store
                 .getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
     }

     /**
      * Parses a 2.1.x <code>string</code> attribute of the
      * <code>keyBinding</code> element.
      *
      * @param string
      * The string to parse; must not be <code>null</code>.
      * @return An array of integer values -- each integer representing a single
      * key stroke. This array may be empty, but it is never
      * <code>null</code>.
      */
     private static final int[] parse2_1Sequence(final String string) {
         final StringTokenizer stringTokenizer = new StringTokenizer (string);
         final int length = stringTokenizer.countTokens();
         final int[] strokes = new int[length];

         for (int i = 0; i < length; i++) {
             strokes[i] = parse2_1Stroke(stringTokenizer.nextToken());
         }

         return strokes;
     }

     /**
      * Parses a single 2.1.x key stroke string, as provided by
      * <code>parse2_1Sequence</code>.
      *
      * @param string
      * The string to parse; must not be <code>null</code>.
      * @return An single integer value representing this key stroke.
      */
     private static final int parse2_1Stroke(final String string) {
         final StringTokenizer stringTokenizer = new StringTokenizer (string,
                 KeyStroke.KEY_DELIMITER, true);

         // Copy out the tokens so we have random access.
 final int size = stringTokenizer.countTokens();
         final String [] tokens = new String [size];
         for (int i = 0; stringTokenizer.hasMoreTokens(); i++) {
             tokens[i] = stringTokenizer.nextToken();
         }

         int value = 0;
         if (size % 2 == 1) {
             String token = tokens[size - 1];
             final Integer integer = (Integer ) r2_1KeysByName.get(token
                     .toUpperCase());

             if (integer != null) {
                 value = integer.intValue();
             } else if (token.length() == 1) {
                 value = token.toUpperCase().charAt(0);
             }

             if (value != 0) {
                 for (int i = 0; i < size - 1; i++) {
                     token = tokens[i];

                     if (i % 2 == 0) {
                         if (token.equalsIgnoreCase(IKeyLookup.CTRL_NAME)) {
                             if ((value & SWT.CTRL) != 0) {
                                 return 0;
                             }

                             value |= SWT.CTRL;

                         } else if (token.equalsIgnoreCase(IKeyLookup.ALT_NAME)) {
                             if ((value & SWT.ALT) != 0) {
                                 return 0;
                             }

                             value |= SWT.ALT;

                         } else if (token
                                 .equalsIgnoreCase(IKeyLookup.SHIFT_NAME)) {
                             if ((value & SWT.SHIFT) != 0) {
                                 return 0;
                             }

                             value |= SWT.SHIFT;

                         } else if (token
                                 .equalsIgnoreCase(IKeyLookup.COMMAND_NAME)) {
                             if ((value & SWT.COMMAND) != 0) {
                                 return 0;
                             }

                             value |= SWT.COMMAND;

                         } else {
                             return 0;

                         }

                     } else if (!KeyStroke.KEY_DELIMITER.equals(token)) {
                         return 0;
                     }
                 }
             }
         }

         return value;
     }

     /**
      * <p>
      * Reads the registry and the preference store, and determines the
      * identifier for the scheme that should be active. There is a complicated
      * order of priorities for this. The registry will only be read if there is
      * no user preference, and the default active scheme id is different than
      * the default default active scheme id.
      * </p>
      * <ol>
      * <li>A non-default preference.</li>
      * <li>The legacy preference XML memento.</li>
      * <li>A default preference value that is different than the default
      * default active scheme id.</li>
      * <li>The registry.</li>
      * <li>The default default active scheme id.</li>
      * </ol>
      *
      * @param configurationElements
      * The configuration elements from the commands extension point;
      * must not be <code>null</code>.
      * @param configurationElementCount
      * The number of configuration elements that are really in the
      * array.
      * @param preferences
      * The memento wrapping the commands preference key; may be
      * <code>null</code>.
      * @param bindingManager
      * The binding manager that should be updated with the active
      * scheme. This binding manager must already have its schemes
      * defined. This value must not be <code>null</code>.
      */
     private static final void readActiveScheme(
             final IConfigurationElement[] configurationElements,
             final int configurationElementCount, final IMemento preferences,
             final BindingManager bindingManager) {
         // A non-default preference.
 final IPreferenceStore store = PlatformUI.getPreferenceStore();
         final String defaultActiveSchemeId = store
                 .getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
         final String preferenceActiveSchemeId = store
                 .getString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
         if ((preferenceActiveSchemeId != null)
                 && (!preferenceActiveSchemeId.equals(defaultActiveSchemeId))) {
             try {
                 bindingManager.setActiveScheme(bindingManager
                         .getScheme(preferenceActiveSchemeId));
                 return;
             } catch (final NotDefinedException e) {
                 // Let's keep looking....
 }
         }

         // A legacy preference XML memento.
 if (preferences != null) {
             final IMemento[] preferenceMementos = preferences
                     .getChildren(TAG_ACTIVE_KEY_CONFIGURATION);
             int preferenceMementoCount = preferenceMementos.length;
             for (int i = preferenceMementoCount - 1; i >= 0; i--) {
                 final IMemento memento = preferenceMementos[i];
                 String id = memento.getString(ATT_KEY_CONFIGURATION_ID);
                 if (id != null) {
                     try {
                         bindingManager.setActiveScheme(bindingManager
                                 .getScheme(id));
                         return;
                     } catch (final NotDefinedException e) {
                         // Let's keep looking....
 }
                 }
             }
         }

         // A default preference value that is different than the default.
 if ((defaultActiveSchemeId != null && defaultActiveSchemeId.length() > 0)
                 && (!defaultActiveSchemeId
                         .equals(IBindingService.DEFAULT_DEFAULT_ACTIVE_SCHEME_ID))) {
             try {
                 bindingManager.setActiveScheme(bindingManager
                         .getScheme(defaultActiveSchemeId));
                 return;
             } catch (final NotDefinedException e) {
                 // Let's keep looking....
 }
         }

         // The registry.
 for (int i = configurationElementCount - 1; i >= 0; i--) {
             final IConfigurationElement configurationElement = configurationElements[i];

             String id = configurationElement
                     .getAttribute(ATT_KEY_CONFIGURATION_ID);
             if (id != null) {
                 try {
                     bindingManager
                             .setActiveScheme(bindingManager.getScheme(id));
                     return;
                 } catch (final NotDefinedException e) {
                     // Let's keep looking....
 }
             }

             id = configurationElement.getAttribute(ATT_VALUE);
             if (id != null) {
                 try {
                     bindingManager
                             .setActiveScheme(bindingManager.getScheme(id));
                     return;
                 } catch (final NotDefinedException e) {
                     // Let's keep looking....
 }
             }
         }

         // The default default active scheme id.
 try {
             bindingManager
                     .setActiveScheme(bindingManager
                             .getScheme(IBindingService.DEFAULT_DEFAULT_ACTIVE_SCHEME_ID));
         } catch (final NotDefinedException e) {
             //this is bad - the default default scheme should always exist
 throw new Error (
                     "The default default active scheme id is not defined."); //$NON-NLS-1$
 }
     }

     /**
      * Reads all of the binding definitions from the preferences.
      *
      * @param preferences
      * The memento for the commands preferences key.
      * @param bindingManager
      * The binding manager to which the bindings should be added;
      * must not be <code>null</code>.
      * @param commandService
      * The command service for the workbench; must not be
      * <code>null</code>.
      */
     private static final void readBindingsFromPreferences(
             final IMemento preferences, final BindingManager bindingManager,
             final ICommandService commandService) {
         List warningsToLog = new ArrayList (1);

         if (preferences != null) {
             final IMemento[] preferenceMementos = preferences
                     .getChildren(TAG_KEY_BINDING);
             int preferenceMementoCount = preferenceMementos.length;
             for (int i = preferenceMementoCount - 1; i >= 0; i--) {
                 final IMemento memento = preferenceMementos[i];

                 // Read out the command id.
 String commandId = readOptional(memento, ATT_COMMAND_ID);
                 if (commandId == null) {
                     commandId = readOptional(memento, ATT_COMMAND);
                 }
                 final Command command;
                 if (commandId != null) {
                     command = commandService.getCommand(commandId);
                 } else {
                     command = null;
                 }

                 // Read out the scheme id.
 String schemeId = readOptional(memento,
                         ATT_KEY_CONFIGURATION_ID);
                 if (schemeId == null) {
                     schemeId = readRequired(memento, ATT_CONFIGURATION,
                             warningsToLog,
                             "Key bindings need a scheme or key configuration"); //$NON-NLS-1$
 if (schemeId == null) {
                         continue;
                     }
                 }

                 // Read out the context id.
 String contextId = readOptional(memento, ATT_CONTEXT_ID);
                 if (contextId == null) {
                     contextId = readOptional(memento, ATT_SCOPE);
                 }
                 if (LEGACY_DEFAULT_SCOPE.equals(contextId)) {
                     contextId = null;
                 }
                 if (contextId == null) {
                     contextId = IContextIds.CONTEXT_ID_WINDOW;
                 }

                 // Read out the key sequence.
 String keySequenceText = readOptional(memento, ATT_KEY_SEQUENCE);
                 KeySequence keySequence = null;
                 if (keySequenceText == null) {
                     keySequenceText = readRequired(memento, ATT_STRING,
                             warningsToLog,
                             "Key bindings need a key sequence or string"); //$NON-NLS-1$
 if (keySequenceText == null) {
                         continue;
                     }

                     // The key sequence is in the old-style format.
 keySequence = convert2_1Sequence(parse2_1Sequence(keySequenceText));

                 } else {
                     // The key sequence is in the new-style format.
 try {
                         keySequence = KeySequence.getInstance(keySequenceText);
                     } catch (final ParseException e) {
                         addWarning(warningsToLog, "Could not parse", null, //$NON-NLS-1$
 commandId, "keySequence", keySequenceText); //$NON-NLS-1$
 continue;
                     }
                     if (keySequence.isEmpty() || !keySequence.isComplete()) {
                         addWarning(
                                 warningsToLog,
                                 "Key bindings cannot use an empty or incomplete key sequence", //$NON-NLS-1$
 null, commandId, "keySequence", keySequence //$NON-NLS-1$
 .toString());
                         continue;
                     }

                 }

                 // Read out the locale and platform.
 final String locale = readOptional(memento, ATT_LOCALE);
                 final String platform = readOptional(memento, ATT_PLATFORM);

                 // Read out the parameters
 final ParameterizedCommand parameterizedCommand;
                 if (command == null) {
                     parameterizedCommand = null;
                 } else {
                     parameterizedCommand = readParameters(memento,
                             warningsToLog, command);
                 }

                 final Binding binding = new KeyBinding(keySequence,
                         parameterizedCommand, schemeId, contextId, locale,
                         platform, null, Binding.USER);
                 bindingManager.addBinding(binding);
             }
         }

         // If there were any warnings, then log them now.
 logWarnings(warningsToLog,
                 "Warnings while parsing the key bindings from the preference store"); //$NON-NLS-1$
 }

     /**
      * Reads all of the binding definitions from the commands extension point.
      *
      * @param configurationElements
      * The configuration elements in the commands extension point;
      * must not be <code>null</code>, but may be empty.
      * @param configurationElementCount
      * The number of configuration elements that are really in the
      * array.
      * @param bindingManager
      * The binding manager to which the bindings should be added;
      * must not be <code>null</code>.
      * @param commandService
      * The command service for the workbench; must not be
      * <code>null</code>.
      */
     private static final void readBindingsFromRegistry(
             final IConfigurationElement[] configurationElements,
             final int configurationElementCount,
             final BindingManager bindingManager,
             final ICommandService commandService) {
         final Collection bindings = new ArrayList (configurationElementCount);
         final List warningsToLog = new ArrayList (1);

         for (int i = 0; i < configurationElementCount; i++) {
             final IConfigurationElement configurationElement = configurationElements[i];

             /*
              * Read out the command id. Doing this before determining if the key
              * binding is actually valid is a bit wasteful. However, it is
              * helpful to have the command identifier when logging syntax
              * errors.
              */
             String commandId = configurationElement
                     .getAttribute(ATT_COMMAND_ID);
             if ((commandId == null) || (commandId.length() == 0)) {
                 commandId = configurationElement.getAttribute(ATT_COMMAND);
             }
             if ((commandId != null) && (commandId.length() == 0)) {
                 commandId = null;
             }
             final Command command;
             if (commandId != null) {
                 command = commandService.getCommand(commandId);
                 if (!command.isDefined()) {
                     // Reference to an undefined command. This is invalid.
 addWarning(warningsToLog,
                             "Cannot bind to an undefined command", //$NON-NLS-1$
 configurationElement, commandId);
                     continue;
                 }
             } else {
                 command = null;
             }

             // Read out the scheme id.
 String schemeId = configurationElement.getAttribute(ATT_SCHEME_ID);
             if ((schemeId == null) || (schemeId.length() == 0)) {
                 schemeId = configurationElement
                         .getAttribute(ATT_KEY_CONFIGURATION_ID);
                 if ((schemeId == null) || (schemeId.length() == 0)) {
                     schemeId = configurationElement
                             .getAttribute(ATT_CONFIGURATION);
                     if ((schemeId == null) || (schemeId.length() == 0)) {
                         // The scheme id should never be null. This is invalid.
 addWarning(warningsToLog, "Key bindings need a scheme", //$NON-NLS-1$
 configurationElement, commandId);
                         continue;
                     }
                 }
             }

             // Read out the context id.
 String contextId = configurationElement
                     .getAttribute(ATT_CONTEXT_ID);
             if (LEGACY_DEFAULT_SCOPE.equals(contextId)) {
                 contextId = null;
             } else if ((contextId == null) || (contextId.length() == 0)) {
                 contextId = configurationElement.getAttribute(ATT_SCOPE);
                 if (LEGACY_DEFAULT_SCOPE.equals(contextId)) {
                     contextId = null;
                 }
             }
             if ((contextId == null) || (contextId.length() == 0)) {
                 contextId = IContextIds.CONTEXT_ID_WINDOW;
             }

             // Read out the key sequence.
 KeySequence keySequence = null;
             String keySequenceText = configurationElement
                     .getAttribute(ATT_SEQUENCE);
             if ((keySequenceText == null) || (keySequenceText.length() == 0)) {
                 keySequenceText = configurationElement
                         .getAttribute(ATT_KEY_SEQUENCE);
             }
             if ((keySequenceText == null) || (keySequenceText.length() == 0)) {
                 keySequenceText = configurationElement.getAttribute(ATT_STRING);
                 if ((keySequenceText == null)
                         || (keySequenceText.length() == 0)) {
                     // The key sequence should never be null. This is pointless
 addWarning(
                             warningsToLog,
                             "Defining a key binding with no key sequence has no effect", //$NON-NLS-1$
 configurationElement, commandId);
                     continue;
                 }

                 // The key sequence is in the old-style format.
 try {
                     keySequence = convert2_1Sequence(parse2_1Sequence(keySequenceText));
                 } catch (final IllegalArgumentException e) {
                     addWarning(warningsToLog, "Could not parse key sequence", //$NON-NLS-1$
 configurationElement, commandId, "keySequence", //$NON-NLS-1$
 keySequenceText);
                     continue;
                 }

             } else {
                 // The key sequence is in the new-style format.
 try {
                     keySequence = KeySequence.getInstance(keySequenceText);
                 } catch (final ParseException e) {
                     addWarning(warningsToLog, "Could not parse key sequence", //$NON-NLS-1$
 configurationElement, commandId, "keySequence", //$NON-NLS-1$
 keySequenceText);
                     continue;
                 }
                 if (keySequence.isEmpty() || !keySequence.isComplete()) {
                     addWarning(
                             warningsToLog,
                             "Key bindings should not have an empty or incomplete key sequence", //$NON-NLS-1$
 configurationElement, commandId, "keySequence", //$NON-NLS-1$
 keySequence.toString());
                     continue;
                 }

             }

             // Read out the locale and platform.
 String locale = configurationElement.getAttribute(ATT_LOCALE);
             if ((locale != null) && (locale.length() == 0)) {
                 locale = null;
             }
             String platform = configurationElement.getAttribute(ATT_PLATFORM);
             if ((platform != null) && (platform.length() == 0)) {
                 platform = null;
             }

             // Read out the parameters, if any.
 final ParameterizedCommand parameterizedCommand;
             if (command == null) {
                 parameterizedCommand = null;
             } else {
                 parameterizedCommand = readParameters(configurationElement,
                         warningsToLog, command);
             }

             final Binding binding = new KeyBinding(keySequence,
                     parameterizedCommand, schemeId, contextId, locale,
                     platform, null, Binding.SYSTEM);
             bindings.add(binding);
         }

         final Binding[] bindingArray = (Binding[]) bindings
                 .toArray(new Binding[bindings.size()]);
         bindingManager.setBindings(bindingArray);

         logWarnings(
                 warningsToLog,
                 "Warnings while parsing the key bindings from the 'org.eclipse.ui.commands' extension point"); //$NON-NLS-1$
 }

     /**
      * Reads all of the scheme definitions from the registry.
      *
      * @param configurationElements
      * The configuration elements in the commands extension point;
      * must not be <code>null</code>, but may be empty.
      * @param configurationElementCount
      * The number of configuration elements that are really in the
      * array.
      * @param bindingManager
      * The binding manager to which the schemes should be added; must
      * not be <code>null</code>.
      */
     private static final void readSchemesFromRegistry(
             final IConfigurationElement[] configurationElements,
             final int configurationElementCount,
             final BindingManager bindingManager) {
         // Undefine all the previous handle objects.
 final HandleObject[] handleObjects = bindingManager.getDefinedSchemes();
         if (handleObjects != null) {
             for (int i = 0; i < handleObjects.length; i++) {
                 handleObjects[i].undefine();
             }
         }

         final List warningsToLog = new ArrayList (1);

         for (int i = 0; i < configurationElementCount; i++) {
             final IConfigurationElement configurationElement = configurationElements[i];

             // Read out the attributes.
 final String id = readRequired(configurationElement, ATT_ID,
                     warningsToLog, "Schemes need an id"); //$NON-NLS-1$
 if (id == null) {
                 continue;
             }
             final String name = readRequired(configurationElement, ATT_NAME,
                     warningsToLog, "A scheme needs a name", id); //$NON-NLS-1$
 if (name == null) {
                 continue;
             }
             final String description = readOptional(configurationElement,
                     ATT_DESCRIPTION);

             String parentId = configurationElement.getAttribute(ATT_PARENT_ID);
             if ((parentId != null) && (parentId.length() == 0)) {
                 parentId = configurationElement.getAttribute(ATT_PARENT);
                 if ((parentId != null) && (parentId.length() == 0)) {
                     parentId = null;
                 }
             }

             // Define the scheme.
 final Scheme scheme = bindingManager.getScheme(id);
             scheme.define(name, description, parentId);
         }

         logWarnings(
                 warningsToLog,
                 "Warnings while parsing the key bindings from the 'org.eclipse.ui.bindings', 'org.eclipse.ui.acceleratorConfigurations' and 'org.eclipse.ui.commands' extension point"); //$NON-NLS-1$
 }

     /**
      * Writes the given active scheme and bindings to the preference store. Only
      * bindings that are of the <code>Binding.USER</code> type will be
      * written; the others will be ignored.
      *
      * @param activeScheme
      * The scheme which should be persisted; may be <code>null</code>.
      * @param bindings
      * The bindings which should be persisted; may be
      * <code>null</code>
      * @throws IOException
      * If something happens while trying to write to the workbench
      * preference store.
      */
     static final void write(final Scheme activeScheme, final Binding[] bindings)
             throws IOException {
         // Print out debugging information, if requested.
 if (DEBUG) {
             Tracing.printTrace("BINDINGS", "Persisting active scheme '" //$NON-NLS-1$ //$NON-NLS-2$
 + activeScheme.getId() + '\'');
             Tracing.printTrace("BINDINGS", "Persisting bindings"); //$NON-NLS-1$ //$NON-NLS-2$
 }

         // Write the simple preference key to the UI preference store.
 writeActiveScheme(activeScheme);

         // Build the XML block for writing the bindings and active scheme.
 final XMLMemento xmlMemento = XMLMemento
                 .createWriteRoot(EXTENSION_COMMANDS);
         if (activeScheme != null) {
             writeActiveSchemeToPreferences(xmlMemento, activeScheme);
         }
         if (bindings != null) {
             final int bindingsLength = bindings.length;
             for (int i = 0; i < bindingsLength; i++) {
                 final Binding binding = bindings[i];
                 if (binding.getType() == Binding.USER) {
                     writeBindingToPreferences(xmlMemento, binding);
                 }
             }
         }

         // Write the XML block to the workbench preference store.
 final IPreferenceStore preferenceStore = WorkbenchPlugin.getDefault()
                 .getPreferenceStore();
         final Writer writer = new StringWriter ();
         try {
             xmlMemento.save(writer);
             preferenceStore.setValue(EXTENSION_COMMANDS, writer.toString());
         } finally {
             writer.close();
         }
     }

     /**
      * Writes the active scheme to its own preference key. This key is used by
      * RCP applications as part of their plug-in customization.
      *
      * @param scheme
      * The scheme to write to the preference store. If the scheme is
      * <code>null</code>, then it is removed.
      */
     private static final void writeActiveScheme(final Scheme scheme) {
         final IPreferenceStore store = PlatformUI.getPreferenceStore();
         final String schemeId = (scheme == null) ? null : scheme.getId();
         final String defaultSchemeId = store
                 .getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
         if ((defaultSchemeId == null) ? (scheme != null) : (!defaultSchemeId
                 .equals(schemeId))) {
             store.setValue(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID,
                     scheme.getId());
         } else {
             store
                     .setToDefault(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
         }
     }

     /**
      * Writes the active scheme to the memento. If the scheme is
      * <code>null</code>, then all schemes in the memento are removed.
      *
      * @param memento
      * The memento to which the scheme should be written; must not be
      * <code>null</code>.
      * @param scheme
      * The scheme that should be written; must not be
      * <code>null</code>.
      */
     private static final void writeActiveSchemeToPreferences(
             final IMemento memento, final Scheme scheme) {
         // Add this active scheme, if it is not the default.
 final IPreferenceStore store = PlatformUI.getPreferenceStore();
         final String schemeId = scheme.getId();
         final String defaultSchemeId = store
                 .getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
         if ((defaultSchemeId == null) ? (schemeId != null) : (!defaultSchemeId
                 .equals(schemeId))) {
             final IMemento child = memento
                     .createChild(TAG_ACTIVE_KEY_CONFIGURATION);
             child.putString(ATT_KEY_CONFIGURATION_ID, schemeId);
         }
     }

     /**
      * Writes the binding to the memento. This creates a new child element on
      * the memento, and places the properties of the binding as its attributes.
      *
      * @param parent
      * The parent memento for the binding element; must not be
      * <code>null</code>.
      * @param binding
      * The binding to write; must not be <code>null</code>.
      */
     private static final void writeBindingToPreferences(final IMemento parent,
             final Binding binding) {
         final IMemento element = parent.createChild(TAG_KEY_BINDING);
         element.putString(ATT_CONTEXT_ID, binding.getContextId());
         final ParameterizedCommand parameterizedCommand = binding
                 .getParameterizedCommand();
         final String commandId = (parameterizedCommand == null) ? null
                 : parameterizedCommand.getId();
         element.putString(ATT_COMMAND_ID, commandId);
         element.putString(ATT_KEY_CONFIGURATION_ID, binding.getSchemeId());
         element.putString(ATT_KEY_SEQUENCE, binding.getTriggerSequence()
                 .toString());
         element.putString(ATT_LOCALE, binding.getLocale());
         element.putString(ATT_PLATFORM, binding.getPlatform());
         if (parameterizedCommand != null) {
             final Map parameterizations = parameterizedCommand
                     .getParameterMap();
             final Iterator parameterizationItr = parameterizations.entrySet()
                     .iterator();
             while (parameterizationItr.hasNext()) {
                 final Map.Entry entry = (Map.Entry ) parameterizationItr.next();
                 final String id = (String ) entry.getKey();
                 final String value = (String ) entry.getValue();
                 final IMemento parameterElement = element
                         .createChild(TAG_PARAMETER);
                 parameterElement.putString(ATT_ID, id);
                 parameterElement.putString(ATT_VALUE, value);
             }
         }
     }

     /**
      * The binding manager which should be populated with the values from the
      * registry and preference store; must not be <code>null</code>.
      */
     private final BindingManager bindingManager;

     /**
      * The command service for the workbench; must not be <code>null</code>.
      */
     private final ICommandService commandService;

     /**
      * Constructs a new instance of <code>BindingPersistence</code>.
      *
      * @param bindingManager
      * The binding manager which should be populated with the values
      * from the registry and preference store; must not be
      * <code>null</code>.
      * @param commandService
      * The command service for the workbench; must not be
      * <code>null</code>.
      */
     BindingPersistence(final BindingManager bindingManager,
             final ICommandService commandService) {
         this.bindingManager = bindingManager;
         this.commandService = commandService;
     }

     protected final boolean isChangeImportant(final IRegistryChangeEvent event) {
         final IExtensionDelta[] acceleratorConfigurationDeltas = event
                 .getExtensionDeltas(
                         PlatformUI.PLUGIN_ID,
                         IWorkbenchRegistryConstants.PL_ACCELERATOR_CONFIGURATIONS);
         if (acceleratorConfigurationDeltas.length == 0) {
             final IExtensionDelta[] bindingDeltas = event.getExtensionDeltas(
                     PlatformUI.PLUGIN_ID,
                     IWorkbenchRegistryConstants.PL_BINDINGS);
             if (bindingDeltas.length == 0) {
                 final IExtensionDelta[] commandDeltas = event
                         .getExtensionDeltas(PlatformUI.PLUGIN_ID,
                                 IWorkbenchRegistryConstants.PL_COMMANDS);
                 if (commandDeltas.length == 0) {
                     final IExtensionDelta[] acceleratorScopeDeltas = event
                             .getExtensionDeltas(
                                     PlatformUI.PLUGIN_ID,
                                     IWorkbenchRegistryConstants.PL_ACCELERATOR_SCOPES);
                     if (acceleratorScopeDeltas.length == 0) {
                         final IExtensionDelta[] contextDeltas = event
                                 .getExtensionDeltas(PlatformUI.PLUGIN_ID,
                                         IWorkbenchRegistryConstants.PL_CONTEXTS);
                         if (contextDeltas.length == 0) {
                             final IExtensionDelta[] actionDefinitionDeltas = event
                                     .getExtensionDeltas(
                                             PlatformUI.PLUGIN_ID,
                                             IWorkbenchRegistryConstants.PL_ACTION_DEFINITIONS);
                            if (actionDefinitionDeltas.length == 0) {
                                return false;
                            }
                        }
                    }
                }
            }
        }

        return true;
    }

    protected final boolean isChangeImportant(final PropertyChangeEvent event) {
        return EXTENSION_COMMANDS.equals(event.getProperty());
    }

    /**
     * Reads all of the binding information from the registry and from the
     * preference store.
     */
    protected final void read() {
        super.read();

        // Create the extension registry mementos.
 final IExtensionRegistry registry = Platform.getExtensionRegistry();
        int activeSchemeElementCount = 0;
        int bindingDefinitionCount = 0;
        int schemeDefinitionCount = 0;
        final IConfigurationElement[][] indexedConfigurationElements = new IConfigurationElement[3][];

        // Sort the bindings extension point based on element name.
 final IConfigurationElement[] bindingsExtensionPoint = registry
                .getConfigurationElementsFor(EXTENSION_BINDINGS);
        for (int i = 0; i < bindingsExtensionPoint.length; i++) {
            final IConfigurationElement configurationElement = bindingsExtensionPoint[i];
            final String name = configurationElement.getName();

            // Check if it is a binding definition.
 if (TAG_KEY.equals(name)) {
                addElementToIndexedArray(configurationElement,
                        indexedConfigurationElements,
                        INDEX_BINDING_DEFINITIONS, bindingDefinitionCount++);
            } else
            // Check to see if it is a scheme definition.
 if (TAG_SCHEME.equals(name)) {
                addElementToIndexedArray(configurationElement,
                        indexedConfigurationElements, INDEX_SCHEME_DEFINITIONS,
                        schemeDefinitionCount++);
            }

        }

        // Sort the commands extension point based on element name.
 final IConfigurationElement[] commandsExtensionPoint = registry
                .getConfigurationElementsFor(EXTENSION_COMMANDS);
        for (int i = 0; i < commandsExtensionPoint.length; i++) {
            final IConfigurationElement configurationElement = commandsExtensionPoint[i];
            final String name = configurationElement.getName();

            // Check if it is a binding definition.
 if (TAG_KEY_BINDING.equals(name)) {
                addElementToIndexedArray(configurationElement,
                        indexedConfigurationElements,
                        INDEX_BINDING_DEFINITIONS, bindingDefinitionCount++);

                // Check if it is a scheme defintion.
 } else if (TAG_KEY_CONFIGURATION.equals(name)) {
                addElementToIndexedArray(configurationElement,
                        indexedConfigurationElements, INDEX_SCHEME_DEFINITIONS,
                        schemeDefinitionCount++);

                // Check if it is an active scheme identifier.
 } else if (TAG_ACTIVE_KEY_CONFIGURATION.equals(name)) {
                addElementToIndexedArray(configurationElement,
                        indexedConfigurationElements, INDEX_ACTIVE_SCHEME,
                        activeSchemeElementCount++);
            }
        }

        /*
         * Sort the accelerator configuration extension point into the scheme
         * definitions.
         */
        final IConfigurationElement[] acceleratorConfigurationsExtensionPoint = registry
                .getConfigurationElementsFor(EXTENSION_ACCELERATOR_CONFIGURATIONS);
        for (int i = 0; i < acceleratorConfigurationsExtensionPoint.length; i++) {
            final IConfigurationElement configurationElement = acceleratorConfigurationsExtensionPoint[i];
            final String name = configurationElement.getName();

            // Check if the name matches the accelerator configuration element
 if (TAG_ACCELERATOR_CONFIGURATION.equals(name)) {
                addElementToIndexedArray(configurationElement,
                        indexedConfigurationElements, INDEX_SCHEME_DEFINITIONS,
                        schemeDefinitionCount++);
            }
        }

        // Create the preference memento.
 final IPreferenceStore store = WorkbenchPlugin.getDefault()
                .getPreferenceStore();
        final String preferenceString = store.getString(EXTENSION_COMMANDS);
        IMemento preferenceMemento = null;
        if ((preferenceString != null) && (preferenceString.length() > 0)) {
            final Reader reader = new StringReader (preferenceString);
            try {
                preferenceMemento = XMLMemento.createReadRoot(reader);
            } catch (final WorkbenchException e) {
                // Could not initialize the preference memento.
 }
        }

        // Read the scheme definitions.
 readSchemesFromRegistry(
                indexedConfigurationElements[INDEX_SCHEME_DEFINITIONS],
                schemeDefinitionCount, bindingManager);
        readActiveScheme(indexedConfigurationElements[INDEX_ACTIVE_SCHEME],
                activeSchemeElementCount, preferenceMemento, bindingManager);
        readBindingsFromRegistry(
                indexedConfigurationElements[INDEX_BINDING_DEFINITIONS],
                bindingDefinitionCount, bindingManager, commandService);
        readBindingsFromPreferences(preferenceMemento, bindingManager,
                commandService);
    }
}

